#include "Utils.h"

#include <codecvt>
#include <exception>
#include <iostream>
#include <locale>
#include <mutex>
#include <vector>

//#include "opencv2/imgcodecs/legacy/constants_c.h"
//#include "opencv2/opencv.hpp"
//#include "opencv2/imgproc/types_c.h"

extern "C" {

#include <ft2build.h>
#include FT_FREETYPE_H
const char *FreeTypeErrorMessage(FT_Error err) {
#undef FTERRORS_H_
#define FT_ERRORDEF(e, v, s)                                                   \
    case e:                                                                      \
        return s;
#define FT_ERROR_START_LIST switch (err) {
#define FT_ERROR_END_LIST }
#include FT_ERRORS_H
    return "(Unknown error)";
}
}

#define CHECK_FREETYPE(x)                                                      \
{                                                                            \
    auto error = x;                                                            \
    if (error) {                                                               \
      auto err_msg = FreeTypeErrorMessage(error);                              \
      std::cerr << "FreeType Error: " << err_msg << std::endl;                 \
      throw std::runtime_error(err_msg);                                       \
    }                                                                          \
}

#define SKIP_PIXELS (3)
namespace {
const stYuvColor s_color_table[YUV_COLOR_MAX] = {
	{0x00, 0x00, 0x00}, // green
	{0x00, 0x00, 0xff}, // red
	{0x00, 0xff, 0x00},	// blue
	{0x00, 0xff, 0xff},	// purple
	{0xff, 0x00, 0x00}, // dark green
	{0xff, 0x00, 0xff}, // yellow
	{0xff, 0xff, 0x00}, // light blue
	{0xff, 0xff, 0xff}, // light purple
	{0x00, 0x80, 0x80}, // dark black
	{0x80, 0x80, 0x80}, // gray
	{0xff, 0x80, 0x80}, // white
};

} 

uint32_t RunStatus::vencChannel_ = 255;

void Utils::YuvSetYData(uint8_t* YBuff, uint8_t* UVBuff, uint16_t width, uint16_t height,
                     	enYuvType yuvType, stPoint draw_point, enYuvColorIdx clrIdx)
{
	switch(yuvType)
	{
		case TYPE_YUV422I_UYVY:
		case TYPE_YUV422I_YUYV:
		{
			/*
				UYVY UYVY UYVY UYVY
			*/
			uint32_t tmp = draw_point.y * width * 2;
			uint32_t y_offset = 0, u_offset = 0, v_offset = 0;
			if(yuvType == TYPE_YUV422I_UYVY) {
				u_offset = tmp + draw_point.x / 2 * 4;
				v_offset = u_offset + 2;
				y_offset = u_offset + 1;
			}
			else {
				y_offset = tmp + draw_point.x / 2 * 4;
				u_offset = y_offset + 1;
				v_offset = u_offset + 2;
			}
			YBuff[y_offset] = s_color_table[clrIdx].Y;
			YBuff[y_offset + 2] = s_color_table[clrIdx].Y;			
		}break;
		case TYPE_YUV420SP_NV12:
		case TYPE_YUV420SP_NV21:
		{
			/*
				YY YY
				YY YY
				UV UV
			*/
			uint32_t y_offset = draw_point.y * width + draw_point.x;
			uint32_t u_offset = 0, v_offset = 0;
			YBuff[y_offset] = s_color_table[clrIdx].Y;
		
			//LogDebug << "[" << draw_point.x << "," << draw_point.y << "]: y_offset = " 
            //         << y_offset << ", u_offset =" <<  u_offset << ", v_offset = " << v_offset;
		}break;
		case TYPE_YUV444P:
		{
			/*
				YYYYYYYY
				UUUUUUUU
				VVVVVVVV
			*/
			uint32_t y_offset = 0, u_offset = 0, v_offset = 0;
			uint32_t plane_size = width * height;
			y_offset = draw_point.y * width + draw_point.x;
			u_offset = y_offset;
			v_offset = plane_size + u_offset;
			YBuff[y_offset] = s_color_table[clrIdx].Y;			
		}break;
		case TYPE_YUV444I:
		{
			/*
				YUV YUV YUV YUV YUV YUV YUV YUV
			*/
			uint32_t y_offset = 0, u_offset = 0, v_offset = 0;
			y_offset = draw_point.y * width * 3 + draw_point.x * 3;
			YBuff[y_offset] = s_color_table[clrIdx].Y;			
		}break;
		case TYPE_YUV422P:
		{
			/*
				YYYYYYYY
				UUUU
				VVVV
			*/
			uint32_t y_offset = 0, u_offset = 0, v_offset = 0;
			uint32_t plane_size = width * height / 2;
			y_offset = draw_point.y * width + draw_point.x;
			YBuff[y_offset] = s_color_table[clrIdx].Y;			
		}break;
	}
}

void Utils::YuvSetData(	uint8_t* YBuff, uint8_t* UVBuff, uint16_t width, uint16_t height,
                     	enYuvType yuvType, stPoint draw_point, enYuvColorIdx clrIdx)
{
	switch(yuvType)
	{
		case TYPE_YUV422I_UYVY:
		case TYPE_YUV422I_YUYV:
		{
			/*
				UYVY UYVY UYVY UYVY
			*/
			uint32_t tmp = draw_point.y * width * 2;
			uint32_t y_offset = 0, u_offset = 0, v_offset = 0;
			if(yuvType == TYPE_YUV422I_UYVY) {
				u_offset = tmp + draw_point.x / 2 * 4;
				v_offset = u_offset + 2;
				y_offset = u_offset + 1;
			}
			else {
				y_offset = tmp + draw_point.x / 2 * 4;
				u_offset = y_offset + 1;
				v_offset = u_offset + 2;
			}
			YBuff[y_offset] = s_color_table[clrIdx].Y;
			YBuff[y_offset + 2] = s_color_table[clrIdx].Y;
			YBuff[u_offset] = s_color_table[clrIdx].U;
			YBuff[v_offset] = s_color_table[clrIdx].V;
		}break;
		case TYPE_YUV420SP_NV12:
		case TYPE_YUV420SP_NV21:
		{
			/*
				YY YY
				YY YY
				UV UV
			*/
			uint32_t y_offset = draw_point.y * width + draw_point.x;
			uint32_t u_offset = 0, v_offset = 0;
			YBuff[y_offset] = s_color_table[clrIdx].Y;
						
			if(yuvType == TYPE_YUV420SP_NV12) {
				u_offset = (draw_point.y / 2) * width + draw_point.x / 2 * 2;
				v_offset = u_offset + 1;
			}
			else {
				v_offset = (draw_point.y / 2) * width + draw_point.x / 2 * 2;
				u_offset = v_offset + 1;
			}
			UVBuff[u_offset] = s_color_table[clrIdx].U;
			UVBuff[v_offset] = s_color_table[clrIdx].V;
			//LogDebug << "[" << draw_point.x << "," << draw_point.y << "]: y_offset = " 
            //         << y_offset << ", u_offset =" <<  u_offset << ", v_offset = " << v_offset;
		}break;
		case TYPE_YUV444P:
		{
			/*
				YYYYYYYY
				UUUUUUUU
				VVVVVVVV
			*/
			uint32_t y_offset = 0, u_offset = 0, v_offset = 0;
			uint32_t plane_size = width * height;
			y_offset = draw_point.y * width + draw_point.x;
			u_offset = y_offset;
			v_offset = plane_size + u_offset;
			YBuff[y_offset] = s_color_table[clrIdx].Y;
			UVBuff[u_offset] = s_color_table[clrIdx].U;
			UVBuff[v_offset] = s_color_table[clrIdx].V;
		}break;
		case TYPE_YUV444I:
		{
			/*
				YUV YUV YUV YUV YUV YUV YUV YUV
			*/
			uint32_t y_offset = 0, u_offset = 0, v_offset = 0;
			y_offset = draw_point.y * width * 3 + draw_point.x * 3;
			u_offset = y_offset + 1;
			v_offset = u_offset + 1;
			YBuff[y_offset] = s_color_table[clrIdx].Y;
			YBuff[u_offset] = s_color_table[clrIdx].U;
			YBuff[v_offset] = s_color_table[clrIdx].V;
		}break;
		case TYPE_YUV422P:
		{
			/*
				YYYYYYYY
				UUUU
				VVVV
			*/
			uint32_t y_offset = 0, u_offset = 0, v_offset = 0;
			uint32_t plane_size = width * height / 2;
			y_offset = draw_point.y * width + draw_point.x;
			u_offset = y_offset / 2;
			v_offset = plane_size + y_offset / 2;
			YBuff[y_offset] = s_color_table[clrIdx].Y;
			UVBuff[u_offset] = s_color_table[clrIdx].U;
			UVBuff[v_offset] = s_color_table[clrIdx].V;
		}break;
	}
}
 
void Utils::YuvDrawLine(stYuvBuffInfo *pYuvBuffInfo, stDrawLineInfo *pDrawLineInfo)
{
	if(!pYuvBuffInfo || !pYuvBuffInfo->pYuvBuff) return;
 
	uint8_t *YBuff = NULL, *UVBuff = NULL;
	uint16_t x0 = pDrawLineInfo->startPoint.x, y0 = pDrawLineInfo->startPoint.y;
	uint16_t x1 = pDrawLineInfo->endPoint.x, y1 = pDrawLineInfo->endPoint.y;
 
	if(pDrawLineInfo->lineWidth == 0) pDrawLineInfo->lineWidth = 1;
	x0 = (x0 >= pYuvBuffInfo->width) ? (x0 - pDrawLineInfo->lineWidth) : x0;
	x1 = (x1 >= pYuvBuffInfo->width) ? (x1 - pDrawLineInfo->lineWidth) : x1;
	y0 = (y0 >= pYuvBuffInfo->height) ? (y0 - pDrawLineInfo->lineWidth) : y0;
	y1 = (y1 >= pYuvBuffInfo->height) ? (y1 - pDrawLineInfo->lineWidth) : y1;
 
	uint16_t dx = (x0 > x1) ? (x0 - x1) : (x1 - x0);
	uint16_t dy = (y0 > y1) ? (y0 - y1) : (y1 - y0);
 
	int16_t xstep = (x0 < x1) ? 1 : -1;
	int16_t ystep = (y0 < y1) ? 1 : -1;
	int16_t nstep = 0, eps = 0;
 
	stPoint draw_point;
	draw_point.x = x0;
	draw_point.y = y0;
 
	switch(pYuvBuffInfo->yuvType)
	{
		case TYPE_YUV422I_UYVY:
		case TYPE_YUV422I_YUYV:
		case TYPE_YUV444I:
            {
                YBuff = pYuvBuffInfo->pYuvBuff;
                UVBuff = NULL;
            }
            break;
		case TYPE_YUV420SP_NV12:
		case TYPE_YUV420SP_NV21:
		case TYPE_YUV444P:
		case TYPE_YUV422P:
            {
                YBuff = pYuvBuffInfo->pYuvBuff;
                UVBuff = pYuvBuffInfo->pYuvBuff + pYuvBuffInfo->width * pYuvBuffInfo->height;
            }
            break;
		default:
			return;
	}
 
	// 鐢啴娴勫Λ顔界溄婵棛鐣诲▔鏇犳暰缁撅拷
	if(dx > dy){
		while(nstep <= dx) {
			YuvSetData(YBuff, UVBuff, pYuvBuffInfo->width, pYuvBuffInfo->height, pYuvBuffInfo->yuvType, draw_point, pDrawLineInfo->clrIdx);
			eps += dy;
			if( (eps << 1) >= dx ) {
				draw_point.y += ystep;
				eps -= dx;
			}
			draw_point.x += xstep;
			nstep++;
		}
	}else {
		while(nstep <= dy){	
			YuvSetData(YBuff, UVBuff, pYuvBuffInfo->width, pYuvBuffInfo->height, pYuvBuffInfo->yuvType, draw_point, pDrawLineInfo->clrIdx);
			eps += dx;
			if( (eps << 1) >= dy ) {
				draw_point.x += xstep;
				eps -= dy;
			}
			draw_point.y += ystep;
			nstep++;
		}
	}
}
 
void Utils::YuvDrawRect(stYuvBuffInfo* yuvBuffInfo, stPoint &startPoint, stPoint &endPoint, enYuvColorIdx clrIdx, int16_t lineSolid)
{
	stDrawLineInfo drawLineInfo;
    
	drawLineInfo.clrIdx = clrIdx;
	drawLineInfo.lineWidth = lineSolid;
    //Draw top line
	drawLineInfo.startPoint.x = startPoint.x;
	drawLineInfo.startPoint.y = startPoint.y;
	drawLineInfo.endPoint.x = endPoint.x;
	drawLineInfo.endPoint.y = startPoint.y;
	YuvDrawLine(yuvBuffInfo, &drawLineInfo);
 
    //Draw bottom line
	drawLineInfo.startPoint.x = startPoint.x;
	drawLineInfo.startPoint.y = endPoint.y;
	drawLineInfo.endPoint.x = endPoint.x;
	drawLineInfo.endPoint.y = endPoint.y;
	YuvDrawLine(yuvBuffInfo, &drawLineInfo);
 
    //Draw left line
	drawLineInfo.startPoint.x = startPoint.x;
	drawLineInfo.startPoint.y = startPoint.y;
	drawLineInfo.endPoint.x = startPoint.x;
	drawLineInfo.endPoint.y = endPoint.y;
	YuvDrawLine(yuvBuffInfo, &drawLineInfo);
 
    //Draw right line
	drawLineInfo.startPoint.x = endPoint.x;
	drawLineInfo.startPoint.y = startPoint.y;
	drawLineInfo.endPoint.x = endPoint.x;
	drawLineInfo.endPoint.y = endPoint.y;
	YuvDrawLine(yuvBuffInfo, &drawLineInfo);
 	
}

class GlpyhContext {
public:
static GlpyhContext &GetInstance() {
        static thread_local GlpyhContext ctx;
        return ctx;
}

int DrawChar(char32_t ch, stPoint &point, enYuvColorIdx clrIdx, stYuvBuffInfo* pYuvBuffInfo) {
	uint8_t *YBuff = NULL, *UVBuff = NULL;
	FT_UInt glyph_idx = 0;
    FT_Face face;
    int font_idx = 0;
    int font_list_size = faces.size();
    for (font_idx = 0; font_idx < font_list_size; ++font_idx) {
        face = faces[font_idx];
        glyph_idx = FT_Get_Char_Index(face, ch);
        CHECK_FREETYPE(FT_Load_Glyph(face, glyph_idx, FT_LOAD_RENDER));
        // if glyph_idx is 0 that means symbol not found,
        // try next font!
        if (glyph_idx > 0) {
            break;
        }
    }

    CHECK_FREETYPE(FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL));

    FT_GlyphSlot slot = face->glyph;

    int h = pYuvBuffInfo->height;
    int w = pYuvBuffInfo->width;

    int image_y = point.y - slot->bitmap_top + font_size;

    switch(pYuvBuffInfo->yuvType)
	{
		case TYPE_YUV422I_UYVY:
		case TYPE_YUV422I_YUYV:
		case TYPE_YUV444I:
			{
				YBuff = pYuvBuffInfo->pYuvBuff;
				UVBuff = NULL;
			}
			break;
		case TYPE_YUV420SP_NV12:
		case TYPE_YUV420SP_NV21:
		case TYPE_YUV444P:
		case TYPE_YUV422P:
			{
				YBuff = pYuvBuffInfo->pYuvBuff;
				UVBuff = pYuvBuffInfo->pYuvBuff + pYuvBuffInfo->width * pYuvBuffInfo->height;
			}
			break;
		default:
			return 0;
	}

    for (int i = 0; i < slot->bitmap.rows && image_y < h; ++i, ++image_y) {
        int image_x = point.x + slot->bitmap_left;
        for (int j = 0; j < slot->bitmap.width && image_x < w; ++j, ++image_x) {
            auto bitmap_val = slot->bitmap.buffer[i * slot->bitmap.width + j];
            if (bitmap_val > 0) {
            	stPoint draw_point;
            	draw_point.x = image_x;
            	draw_point.y = image_y;
				if ((i % SKIP_PIXELS) != 0  || (j % SKIP_PIXELS) != 0 ) {
					Utils::YuvSetData(YBuff, UVBuff, pYuvBuffInfo->width, pYuvBuffInfo->height, pYuvBuffInfo->yuvType, draw_point, clrIdx);
				} else {
					Utils::YuvSetYData(YBuff, UVBuff, pYuvBuffInfo->width, pYuvBuffInfo->height, pYuvBuffInfo->yuvType, draw_point, clrIdx);
				}
                
            }
        }
    }

    //cv::Mat gray_img = cv::Mat(slot->bitmap.rows, slot->bitmap.width, CV_8UC1, slot->bitmap.buffer);
    //cv::imwrite("bitmap.jpg", gray_img);

    return slot->advance.x >> 6;
}

private:
GlpyhContext() {
    std::vector<const char *> font_list{
		//"./DroidSansFallbackFull.ttf"
		"./BabelStoneHan.ttf"
		//"/home/HwHiAiUser/wqy-bsong.ttf"
        //"/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", // For CJK
        //"/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf" // For latin and
                                                              // digits
    	//"/usr/share/fonts/truetype/ubuntu/Ubuntu-MI.ttf"
    };

    for (const char *font_path : font_list) {
        FT_Face face;
        CHECK_FREETYPE(FT_Init_FreeType(&library));
        CHECK_FREETYPE(FT_New_Face(library, font_path, 0, &face));

        CHECK_FREETYPE(FT_Set_Pixel_Sizes(face, font_size, font_size));
        faces.push_back(face);
    }
}

    int font_size{20};
    FT_Library library;
    std::vector<FT_Face> faces;
    std::mutex mtx;
};

void Utils::YuvDrawText(stYuvBuffInfo* yuvBuffInfo, stPoint &point, const std::string &text, const enYuvColorIdx clrIdx)
{
	auto &ctx = GlpyhContext::GetInstance();
	std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> cvt;
	std::u32string utf32_str = cvt.from_bytes(text);
	for (auto u32char : utf32_str) {
		point.x += ctx.DrawChar(u32char, point, clrIdx, yuvBuffInfo);
	}
}

